package com.polito.mad.bikeponyapp.list;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.polito.mad.bikeponyapp.BikePonyApplication.TypeFaceLoader;
import com.polito.mad.bikeponyapp.R;
import com.polito.mad.bikeponyapp.persistence.DeliveriesRepository;
import com.polito.mad.bikeponyapp.persistence.DeliveryItem;

public class ListViewAdapter  extends BaseAdapter
{
	private List<DeliveryItem> items;
	
	private OnShowSingleLocationListener onShowSingleLocationListener;
	private List<Long> itemsClone;
	private boolean editState=false;
	private boolean dirtyRoutingObjects;
	private Context context;
	private boolean redraw;
	private DeliveriesRepository repository;
	
	public ListViewAdapter(Context context,DeliveriesRepository repository) {
		super();
		
		this.context=context;
		dirtyRoutingObjects=true;
		this.repository=repository;
		items = this.repository.getSessionCachedItems();
		itemsClone = this.repository.getItemsClone();
	}
	
	
	@Override
	public Object getItem(int position) {
		return items.get(position);
	}
	
	@Override
	public long getItemId(int position) {
		return position;
	}
	@Override
	public int getCount() {
		return items == null?0:items.size();
	}
	
	
	/**
	 * swap the elements involed in a Drag operation when an element as to be interchnged with another one.
	 * Properly update the underlying model in order to reflect the new disposal of objects
	 * 
	 * 
	 * @param fromPosition
	 * @param targetPosition
	 */
	private synchronized boolean swap(int fromPosition,int targetPosition) {
		
		if(fromPosition == targetPosition)
			return false;
		
		/*
		 * check that the current PivotItem does not violate the constraints:
		 * if it is a pickup task , than it must be before the correspoding delivery task,
		 * else if it is a delivery task , it must follow the pickup task ( if still present)
		 *
		 * DISPLAYS the BOTTOM action bar with save and cancel
		 */
		items.get(targetPosition).setOrder(fromPosition);
		items.get(fromPosition).setOrder(targetPosition);
		if(targetPosition>fromPosition)
		{
			items.add(fromPosition,items.remove(targetPosition));
		}
		else
		{
			items.add(targetPosition,items.remove(fromPosition));
		}
		dirtyRoutingObjects=true;
		return true;
	}
	
	/**
	 * check if a Delivery item can be swapped with another DeliveryItem.
	 * If the items can be exchanged , the operation is applied and the underlying list of items will reflect it. 
	 * 
	 * @param fromPosition
	 * @param targetPosition
	 * @return 
	 * 		true,  if the items can be exchanged
	 * 		false, if the items cannot be exchanged
	 */
	public synchronized boolean checkDrop(int fromPosition,int targetPosition)
	{
		//TODO manage R.string.delivery_id_notification_intent_param in order to know if the topmost delivery item is neing swapped and avoid it
		if(fromPosition==targetPosition)
			return true;
		long pendingNotificationDeliveryItemId  = PreferenceManager.getDefaultSharedPreferences(context).getLong(context.getString(R.string.delivery_id_notification_intent_param), (long)-1);
		
		/*since the from and target dipend from the order of drag,
		 * and we have to check always the first item, we have to know which of the 2 to check against the notification already setup.
		 */
		int positionToCheck = fromPosition-targetPosition>0?targetPosition:fromPosition;
		
		if(items.get(positionToCheck).getId()==pendingNotificationDeliveryItemId)
		{
			// check if the target element and source element can be swapped
			/*
			 * item at fromPosition must be put 
			 * 
			 */
			return false;
		}
		if(fromPosition<targetPosition)
		{
			
			for(int i=fromPosition;i<targetPosition;i++)
				swap(i, i+1);
			
		}else
		{
			for(int i=fromPosition;i>targetPosition;i--)
			{
				swap(i, i-1);
			}
		}
		
		return true;
	}
	
	@Override
	public View getView(final int position, View convertView, ViewGroup parent) {

		if (convertView == null) {
			LayoutInflater li = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
			convertView = li.inflate(R.layout.list_item_layout, null);
			initializeView(convertView, position);
		}
		
		DeliveryItem task = items.get(position);
		Date estimatedTime  = new Date(task.getEstimatedTime());

		TextView address = (TextView) convertView.findViewById(R.id.address);
		address.setText(task.getAddress());
		address.setEllipsize(TextUtils.TruncateAt.END);
		address.setSingleLine();
		address.setHorizontallyScrolling(false);
		((TextView) convertView.findViewById(R.id.type)).setText(task.getDeliveryItemType().toString());
		((TextView) convertView.findViewById(R.id.estimatedTime)).setText(DateFormat.getTimeInstance(DateFormat.SHORT).format(estimatedTime));
		
		final ImageView dragButton = (ImageView)convertView.findViewById(R.id.drag_handle);
		
		/*
		 * When the view are created or have to be updated due to a notifySetChanged() call , 
		 * we should properly reflect the actual status of the underlying model:
		 * 
		 * The model can be in 2 states : -edit , when the drag&drop operation is allowed
		 * 								  -non edit , when the drag&drop is not allowed
		 * 
		 * Depending on which status is actually setup, we hide/show some views to notify the user about the current active state
		 * 
		 */
		
		if(editState && dragButton.getVisibility()==View.GONE)
		{
			
			/*first we hide the moreInfo panel if present*/
			expandDragHandler(dragButton);
		}
		else if(!editState && dragButton.getVisibility()==View.VISIBLE)
		{
			collapseDragHandler(dragButton);
		}

		if(task.isInvalidPosition())
			convertView.setBackgroundColor(0x44ff0000);
		else
			convertView.setBackgroundColor(0x00ffffff);
		
		return convertView;
	}
	
	private void expandDragHandler(final View dragHandler)
	{
		dragHandler.setVisibility(View.VISIBLE);
	}
	
	private void collapseDragHandler(final View dragHandler)
	{
		dragHandler.setVisibility(View.GONE);
	}
	
	/**
	 * 
	 * Thie initialization involves operations to do on the View only once it has to be created
	 * 
	 * @param view
	 * @param position
	 * @return
	 */
	private View initializeView(View view,final int position)
	{
		TypeFaceLoader typeFaceLoader =  TypeFaceLoader.getInstance(context.getAssets());
		TextView address = (TextView) view.findViewById(R.id.address);
		TextView type = (TextView) view.findViewById(R.id.type);
		TextView estimatedTime = (TextView)view.findViewById(R.id.estimatedTime);
		address.setTypeface(typeFaceLoader.getRobotoMediumTypeFace());
		type.setTypeface(typeFaceLoader.getRobotoThinTypeFace());
		estimatedTime.setTypeface(typeFaceLoader.getRobotoLightTypeFace());
		return view;
	}

	
	/**
	 * When model items are fetched form the database using a cursor , 
	 * the service in charge of reading data from the DB will call this method to let the underlying list-Model to update itself
	 * @param c
	 */
	public void updateData(Cursor c)
	{
		/*
		 * If the items cache is available , use it to load the items.
		 * The sursor is set to null by the DeliveryListCursorLoader when it finds that the cache should be used
		 */
		
		if(c==null && repository.getSessionCachedItems()!=null)
		{
			itemsClone= repository.getItemsClone();
			items = repository.getSessionCachedItems();
			notifyDataSetChanged();
			return;
		}
		/*if the cursor is dirty and inconsistent , then return */
		if(c.isAfterLast() && c.isClosed())
			return;
		
		/*prepare the items collection*/
		if(items==null)
			items = new ArrayList<DeliveryItem>();
		else 
			items.clear();
		
		/*prepare the items Id clone to be managed while reordering the list items*/
		if(itemsClone == null)
			itemsClone = new ArrayList<Long>();
		else
			itemsClone.clear();
		
		/*start looping over the cursor to fetch the items*/
		c.moveToFirst();
		while (!c.isAfterLast()) {
			
			DeliveryItem del = repository.getCursor(c);
			items.add(del);
			itemsClone.add(del.getId());
			c.moveToNext();
		}
		/*Once all the items are loaded , we can set them as the useful cache to be used for later uses*/
		repository.setSessionCachedItems(items);
		repository.setItemsClone(itemsClone);
		
		/*be sure to reference the right cached objects to avoid inconsistency*/
		itemsClone = repository.getItemsClone();
		items = repository.getSessionCachedItems();
		
		/*close the Cursor and notify the list Adapter that the items list is changed*/
		c.close();
		notifyDataSetChanged();
	}

	
	/**
	 * Check whether the underlying model is changed from when a Drag operation is started and when it is ended, or not.
	 * This method can be useful in order to avoid the repetition of useless computation if the data didn't change
	 * @return
	 */
	public boolean isOrderChanged() {
		if(itemsClone==null)
			return false;
		for(int i=0;i<items.size();i++)
		{
			DeliveryItem modified = items.get(i);
			if(modified.getId()!=itemsClone.get(i))
				return true;
		}
		return false;
	}
	
	public List<DeliveryItem> getItems()
	{
		return repository.getSessionCachedItems();
	}

	public boolean isEditState() {
		return editState;
	}
	
	/**
	 * 
	 * Start the edit state for the underlying data model.
	 * It involves the creatio of a fast cache to be used for fast comparison like
	 * 
	 * @see isOrderChanged
	 *
	 */
	public void startEditState()
	{
		this.editState=true;
		if(itemsClone == null)
		{
			itemsClone = new ArrayList<Long>();
			for(int i=0;i<items.size();i++)
			{
				itemsClone.add(items.get(i).getId());
			}
			repository.setItemsClone(itemsClone);
			itemsClone= repository.getItemsClone();
		}
		notifyDataSetChanged();
	}
	
	
	/**
	 * Stop the edit state for the underlying data model
	 * @param b 
	 */
	public void endEditState(boolean reset)
	{
		if(reset)
			repository.flushCachedObjects();
		
		this.editState=false;
		notifyDataSetChanged();
	}
	
	/**
	 * Check if the underlying DeliveryItems have somehow changed their states and a rerouting should be recomputed on them
	 * 
	 * @return whether the route for the backed DeliveryItems should be recomputed or not
	 */
	public boolean isDirtyRouting()
	{
		return dirtyRoutingObjects && !items.isEmpty();
	}
	
	public void setItems(List<DeliveryItem> items)
	{
		this.items=items;
		notifyDataSetChanged();
	}
	
	public OnShowSingleLocationListener getOnsShowSingleLocationListener() {
		return onShowSingleLocationListener;
	}

	public void setOnsShowSingleLocationListener(OnShowSingleLocationListener onsShowSingleLocationListener) {
		this.onShowSingleLocationListener = onsShowSingleLocationListener;
	}

	public interface OnShowSingleLocationListener
	{
		public void showSingleItem( int itemIndex);
	}
	
	/**
	 * The items routing has been computed , and we must signal to reset the dirty status 
	 * in order to avoid the recomputation of the Routing at later calls , if not necessary
	 */
	public void resetDirtyRouting()
	{
		this.dirtyRoutingObjects=false;
		this.items = repository.getSessionCachedItems();
		notifyDataSetChanged();
	}
	
	
	public void resetList()
	{
		repository.flushCachedObjects();
	}

	/**
	 * check if the items should be redrawn somehow
	 * @return
	 */
	public boolean isRedraw() {
		return redraw;
	}

	/**
	 * Ask the list to properly manage the request of redrawing the items
	 * @param redraw
	 */
	public void setRedraw(boolean redraw) {
		this.redraw = redraw;
	}
	
	public void setDirtyObjects()
	{
		this.dirtyRoutingObjects= true;
	}

	
	public void validDeliveryReorder() {
		repository.setItemsClone(null);
		this.endEditState(false);
	}
}
